/*
 * MIT License
 *
 * Copyright (c) 2019 Mr.css
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

package cn.seaboot.commons.file;

import cn.seaboot.commons.core.Asserts;
import cn.seaboot.commons.core.ClassPathResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.nio.charset.Charset;
import java.nio.file.Files;

/**
 * 开发中，有很多例如ZIP、response这样特殊的OutputStream，
 * 一个InputStream输入结束，允许再写入另一个的InputStream，
 * 并不是每次写入结束，都需要关闭OutputStream，OutputStream要求在程序运行最终时刻才关闭。
 * （此工具包设计原则，谁创建的Stream实例，谁负责关闭，工具类不关闭 IO 流）
 * <p>
 * This util contain most of IO-copy operations, copies all the data from the given Stream to the Stream.
 * It leaves both streams open, so you will still need to close them once done!
 *
 * @author Mr.css on 2017-8-21
 */
public class IOUtils {
    private static final Logger logger = LoggerFactory.getLogger(IOUtils.class);

    private IOUtils() {
    }

    /**
     * The default buffer size ({@value}) to use in copy methods.
     */
    public static final int DEFAULT_BUFFER_SIZE = 4096;

    /**
     * Represents the end-of-file (or stream).
     */
    public static final int EOF = -1;

    /**
     * Opens a {@link FileOutputStream} for the specified file
     *
     * @param path file path
     * @return OutputStream
     * @throws IOException file is directory or file not found or file can't read
     */
    public static FileOutputStream openFileOutputStream(String path) throws IOException {
        return new FileOutputStream(path);
    }

    /**
     * Opens a {@link FileOutputStream} for the specified file
     *
     * @param file file
     * @return OutputStream
     * @throws IOException file is directory or file not found or file can't read
     */
    public static FileOutputStream openFileOutputStream(File file) throws IOException {
        return new FileOutputStream(file);
    }

    /**
     * Opens a {@link FileInputStream} for the specified file
     *
     * @param path file path
     * @return OutputStream
     * @throws IOException file is directory or file not found or file can't read
     */
    public static FileInputStream openFileInputStream(String path) throws IOException {
        return openFileInputStream(new File(path));
    }

    /**
     * Opens a {@link FileInputStream} for the specified file
     *
     * @param file file
     * @return OutputStream
     * @throws IOException file is directory or file not found or file can't read
     */
    public static FileInputStream openFileInputStream(File file) throws IOException {
        if (file.exists() && file.isFile()) {
            return new FileInputStream(file);
        } else {
            throw new FileNotFoundException("File not exist or it is a directory: " + file);
        }
    }

    /**
     * 关闭一个或多个流对象
     *
     * @param closeable one or more Closeable
     */
    public static void close(Closeable... closeable) throws IOException {
        if (closeable != null) {
            for (Closeable c : closeable) {
                if (c != null) {
                    try {
                        c.close();
                    } catch (Exception e) {
                        logger.error("close closeable failed!", e);
                    }
                }
            }
        }
    }

    /**
     * 关闭一个或多个流对象
     *
     * @param closeable one
     */
    public static void close(Closeable closeable) throws IOException {
        if (closeable != null) {
            closeable.close();
        }
    }

    /**
     * Copies all the data from the given InputStream to the OutputStream.
     * if stream over 2GB, method will return -1,
     * had batter using <dode>copyLarge(InputStream input, OutputStream output)</dode>
     *
     * @param input  -
     * @param output -
     * @return the number of bytes copied
     * @throws IOException -
     */
    public static int copy(InputStream input, OutputStream output) throws IOException {
        long count = copyLarge(input, output);
        if (count > Integer.MAX_VALUE) {
            return -1;
        } else {
            return (int) count;
        }
    }

    /**
     * Copies all the data from the given InputStream to the OutputStream.
     *
     * @param input  -
     * @param output -
     * @return the number of bytes copied
     * @throws IOException -
     */
    public static long copyLarge(InputStream input, OutputStream output) throws IOException {
        return copy(input, output, DEFAULT_BUFFER_SIZE);
    }

    /**
     * Copies all the data from the given InputStream to the OutputStream.
     *
     * @param input      -
     * @param output     -
     * @param bufferSize the buffer to use for the copy
     * @return the number of bytes copied
     * @throws IOException -
     */
    public static long copy(InputStream input, OutputStream output, int bufferSize) throws IOException {
        return copyLarge(input, output, new byte[bufferSize]);
    }

    /**
     * Copies all the data from the given InputStream to the OutputStream.
     *
     * @param input  -
     * @param output -
     * @param buffer the buffer to use for the copy
     * @return the number of bytes copied
     * @throws IOException -
     */
    public static long copyLarge(InputStream input, OutputStream output, byte[] buffer) throws IOException {
        long count = 0;
        int n;
        while (EOF != (n = input.read(buffer))) {
            output.write(buffer, 0, n);
            count += n;
        }
        return count;
    }

    /**
     * Copies all the data from the given InputStream to the Writer.
     *
     * @param input  -
     * @param output -
     * @throws IOException -
     */
    public static void copy(InputStream input, Writer output) throws IOException {
        copy(input, output, Charset.defaultCharset());
    }

    /**
     * Copies all the data from the given InputStream to the Writer.
     *
     * @param input    -
     * @param output   -
     * @param encoding -
     * @throws IOException -
     */
    public static void copy(InputStream input, Writer output, String encoding) throws IOException {
        copy(input, output, Charset.forName(encoding));
    }

    /**
     * Copies all the data from the given InputStream to the Writer.
     *
     * @param input    -
     * @param output   -
     * @param encoding -
     * @throws IOException -
     */
    public static void copy(InputStream input, Writer output, Charset encoding) throws IOException {
        InputStreamReader in = new InputStreamReader(input, encoding);
        copy(in, output);
    }

    /**
     * Copies all the data from the given Reader to the Writer.
     *
     * @param input  -
     * @param output -
     * @return the number of bytes copied
     * @throws IOException -
     */
    public static int copy(Reader input, Writer output) throws IOException {
        long count = copyLarge(input, output);
        if (count > Integer.MAX_VALUE) {
            return -1;
        } else {
            return (int) count;
        }
    }

    /**
     * Copies all the data from the given Reader to the Writer.
     *
     * @param input  -
     * @param output -
     * @return the number of bytes copied
     * @throws IOException -
     */
    public static long copyLarge(Reader input, Writer output) throws IOException {
        return copy(input, output, DEFAULT_BUFFER_SIZE);
    }

    /**
     * Copies all the data from the given Reader to the Writer.
     *
     * @param input      -
     * @param output     -
     * @param bufferSize -
     * @return the number of bytes copied
     * @throws IOException -
     */
    public static long copy(Reader input, Writer output, int bufferSize) throws IOException {
        return copyLarge(input, output, new char[bufferSize]);
    }

    /**
     * Copies all the data from the given Reader to the Writer.
     *
     * @param input  -
     * @param output -
     * @param buffer -
     * @return the number of bytes copied
     * @throws IOException -
     */
    public static long copyLarge(Reader input, Writer output, char[] buffer) throws IOException {
        long count = 0;
        int n;
        while (EOF != (n = input.read(buffer))) {
            output.write(buffer, 0, n);
            count += n;
        }
        return count;
    }

    /**
     * Copies all the data from the given Reader to the OutputStream.
     *
     * @param input  -
     * @param output -
     * @throws IOException -
     */
    public static void copy(Reader input, OutputStream output) throws IOException {
        copy(input, output, Charset.defaultCharset());
    }

    /**
     * Copies all the data from the given Reader to the OutputStream.
     *
     * @param input    -
     * @param output   -
     * @param encoding -
     * @throws IOException -
     */
    public static void copy(Reader input, OutputStream output, String encoding) throws IOException {
        copy(input, output, Charset.forName(encoding));
    }

    /**
     * Copies all the data from the given Reader to the OutputStream.
     *
     * @param input    -
     * @param output   -
     * @param encoding -
     * @throws IOException -
     */
    public static void copy(Reader input, OutputStream output, Charset encoding) throws IOException {
        OutputStreamWriter out = new OutputStreamWriter(output, encoding);
        copy(input, out);
        out.flush();
    }

    /**
     * Convert String to ByteArrayInputStream
     *
     * @param input    a CharSequence
     * @param encoding encoding
     * @return ByteArrayInputStream
     */
    public static ByteArrayInputStream stringToInputStream(CharSequence input, Charset encoding) {
        byte[] bytes = input.toString().getBytes(encoding);
        return new ByteArrayInputStream(bytes);
    }

    /**
     * Convert String to ByteArrayInputStream
     *
     * @param input a String
     * @return ByteArrayInputStream
     */
    public static ByteArrayInputStream stringToInputStream(String input) {
        byte[] bytes = input.getBytes();
        return new ByteArrayInputStream(bytes);
    }
}
